#include <LittleFS.h>
#define FileSys LittleFS

#define GIF_USE 1
#define PNG_USE 0

//====================================================================================
//                                  GIF Content
//====================================================================================
#if GIF_USE == 1
#include <AnimatedGIF.h>
#define DISPLAY_WIDTH  tft.width()
#define DISPLAY_HEIGHT tft.height()
#define BUFFER_SIZE 256
AnimatedGIF gif;
static int iXOff, iYOff;
uint16_t usTemp[1][BUFFER_SIZE]; 
static uint16_t usImage[DISPLAY_WIDTH * DISPLAY_HEIGHT];
void gifDraw(GIFDRAW *pDraw);
void *GifOpen(const char *fname, int32_t *pSize);
void GifClose(void *pHandle);
int32_t GifRead(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen);
int32_t GifSeek(GIFFILE *pFile, int32_t iPosition);
void ShowGif();
#endif

//====================================================================================
//                                  PNG Content
//====================================================================================
#if PNG_USE == 1
#include <PNGdec.h>
PNG png; // PNG decoder instance
void pngDraw(PNGDRAW *pDraw);
void *pngOpen(const char *filename, int32_t *size);
void pngClose(void *handle);
int32_t pngRead(PNGFILE *page, uint8_t *buffer, int32_t length);
int32_t pngSeek(PNGFILE *page, int32_t position);
void ShowPng();
#endif

//====================================================================================
//                                  Universal Content
//====================================================================================
#define MAX_IMAGE_WIDTH 240 // Adjust for your images
File file;
int16_t xpos = 0;
int16_t ypos = 0;

#include <User_Setup_Select.h>
#include "SPI.h"
#include <TFT_eSPI.h>

TFT_eSPI tft = TFT_eSPI();
//====================================================================================
//                                    Setup
//====================================================================================

void setup()
{
    Serial.begin(115200);
    pinMode(22, OUTPUT);
    digitalWrite(22, HIGH);
    if (!FileSys.begin())
    {
        Serial.println("\r\nLittleFS挂载失败\n");
    }
    if (!FileSys.exists("/zero.gif"))
    {
        Serial.println("\r\n不存在目标文件\n");
    }
    // Initialise the TFT
    tft.begin();
    tft.fillScreen(TFT_BLACK);
    gif.begin(LITTLE_ENDIAN_PIXELS);
    Serial.println("\r\nInitialisation done.");
}

//====================================================================================
//                                    Loop
//====================================================================================
void loop()
{
    ShowGif();
}

//====================================================================================
//                                 PNG Function
//====================================================================================
#if PNG_USE == 1
void pngDraw(PNGDRAW *pDraw)
{
    uint16_t lineBuffer[MAX_IMAGE_WIDTH];
    png.getLineAsRGB565(pDraw, lineBuffer, PNG_RGB565_BIG_ENDIAN, 0xffffffff);
    tft.pushImage(xpos, ypos + pDraw->y, pDraw->iWidth, 1, lineBuffer);
}

void *pngOpen(const char *filename, int32_t *size)
{
    Serial.printf("Attempting to open %s\n", filename);
    pngfile = FileSys.open(filename, "r");
    *size = pngfile.size();
    return &pngfile;
}

void pngClose(void *handle)
{
    File pngfile = *((File *)handle);
    if (pngfile)
        pngfile.close();
}

int32_t pngRead(PNGFILE *page, uint8_t *buffer, int32_t length)
{
    if (!pngfile)
        return 0;
    page = page; // Avoid warning
    return pngfile.read(buffer, length);
}

int32_t pngSeek(PNGFILE *page, int32_t position)
{
    if (!pngfile)
        return 0;
    page = page; // Avoid warning
    return pngfile.seek(position);
}

void ShowPng()
{
    File root = LittleFS.open("/", "r");
    while (File file = root.openNextFile())
    {
        String strname = file.name();
        strname = "/" + strname;
        Serial.println(file.name());
        if (!file.isDirectory() && strname.endsWith(".png"))
        {
            int16_t rc = png.open(strname.c_str(), pngOpen, pngClose, pngRead, pngSeek, pngDraw);
            if (rc == PNG_SUCCESS)
            {
                tft.startWrite();
                Serial.printf("image specs: (%d x %d), %d bpp, pixel type: %d\n", png.getWidth(), png.getHeight(), png.getBpp(), png.getPixelType());
                uint32_t dt = millis();
                if (png.getWidth() > MAX_IMAGE_WIDTH)
                {
                    Serial.println("Image too wide for allocated line buffer size!");
                }
                else
                {
                    rc = png.decode(NULL, 0);
                    png.close();
                }
                tft.endWrite();
                // How long did rendering take...
                Serial.print(millis() - dt);
                Serial.println("ms");
            }
        }
        delay(3000);
        tft.fillScreen(random(0x10000));
    }
}
#endif

//====================================================================================
//                                 GIF Function
//====================================================================================

#if GIF_USE == 1
void ShowGif()
{
    int res = gif.open("/zero.gif", GifOpen, GifClose, GifRead, GifSeek, gifDraw);
    if (!res)
    {
        Serial.printf("Error opening file = %d\n", gif.getLastError());
        while (1)
        {
        };
        return;
    }
    GIFINFO gi;
    // 打印文件数据
    Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight());
    res = gif.getInfo(&gi);
    if (!res)
    {
        Serial.printf("获取数据失败");
        while (1)
        {
        };
        return;
    }
    Serial.printf("frame count: %d\n", gi.iFrameCount);
    Serial.printf("duration: %d ms\n", gi.iDuration);
    Serial.printf("max delay: %d ms\n", gi.iMaxDelay);
    Serial.printf("min delay: %d ms\n", gi.iMinDelay);
    iXOff = (DISPLAY_WIDTH - gif.getCanvasWidth()) / 2;
    if (iXOff < 0)
        iXOff = 0;
    iYOff = (DISPLAY_HEIGHT - gif.getCanvasHeight()) / 2;
    if (iYOff < 0)
        iYOff = 0;
    tft.startWrite(); // We assume the LCD has exclusive use of the SPI bus (on PyPortal it does)
    while (gif.playFrame(true, NULL))
    {
    }
    gif.close();
    tft.endWrite();
}

void gifDraw(GIFDRAW *pDraw)
{
    uint8_t *s;
    uint16_t *d, *usPalette;
    int x, iWidth;

    iWidth = pDraw->iWidth;
    if (iWidth + pDraw->iX > DISPLAY_WIDTH)
        iWidth = DISPLAY_WIDTH - pDraw->iX;
    usPalette = pDraw->pPalette;
    if (pDraw->iY + pDraw->y >= DISPLAY_HEIGHT || pDraw->iX >= DISPLAY_WIDTH || iWidth < 1)
        return;
    if (pDraw->y == 0)
    {                  // start of frame, set address window on LCD
        tft.dmaWait(); // wait for previous writes to complete before trying to access the LCD
        tft.setAddrWindow(iXOff + pDraw->iX, iYOff + pDraw->iY, pDraw->iWidth, pDraw->iHeight);
        // By setting the address window to the size of the current GIF frame, we can just write
        // continuously over the whole frame without having to set the address window again
    }
    s = pDraw->pPixels;
    if (pDraw->ucDisposalMethod == 2) // restore to background color
    {
        for (x = 0; x < iWidth; x++)
        {
            if (s[x] == pDraw->ucTransparent)
                s[x] = pDraw->ucBackground;
        }
        pDraw->ucHasTransparency = 0;
    }

    // Apply the new pixels to the main image
    d = &usImage[pDraw->iWidth * pDraw->y];
    if (pDraw->ucHasTransparency) // if transparency used
    {
        uint8_t c, ucTransparent = pDraw->ucTransparent;
        int x;
        for (x = 0; x < iWidth; x++)
        {
            c = *s++;
            if (c != ucTransparent)
            {
                d[x] = usPalette[c];
            }
        }
    }
    else
    {
        // Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
        for (x = 0; x < iWidth; x++)
        {
            d[x] = usPalette[s[x]];
        }
    }
    tft.dmaWait(); // wait for last write to complete (the last scan line)
    // We write with block set to FALSE (3rd param) so that we can be decoding the next
    // line while the DMA hardware continues to write data to the LCD controller
    tft.pushPixels(d, iWidth);
    // tft.pushImage(x + pDraw->iX, y, chunkWidth, 1, chunkBuffer);
}

void *GifOpen(const char *fname, int32_t *pSize)
{
    Serial.printf("Attempting to open %s\n", fname);
    file = FileSys.open(fname, "r");
    *pSize = file.size();
    return &file;
}

void GifClose(void *pHandle)
{
    File gif = *((File *)pHandle);
    if (gif)
    {
        gif.close();
    }
}

int32_t GifRead(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen)
{
    if (!file)
    {
        return 0;
    }
    pFile = pFile; // Avoid warning
    return file.read(pBuf, iLen);
}

int32_t GifSeek(GIFFILE *page, int32_t position)
{
    if (!file)
    {
        return 0;
    }
    page = page; // Avoid warning
    return file.seek(position);
}
#endif